<template>
    <el-card style="margin-bottom: 20px">
        <!-- 查询 -->
        <el-form :model="state.queryParams" ref="queryForm" :inline="true">
            <el-form-item v-for="(item, index) in filteredQueryData" :label="item.columnTitle" :prop="item.fieldName"
                :key="index">
                <el-select v-if="item.htmlType == 'select'" v-model="state.queryParams[item.fieldName]"
                    placeholder="请选择" clearable style="width: 240px">
                    <el-option v-for="dict in state[item.fieldName + 'Option']" :key="dict.dictValue"
                        :label="dict.dictLabel" :value="dict.dictValue" />
                </el-select>

                <el-date-picker v-else-if="item.htmlType == 'datetime'" v-model="state.queryParams[item.fieldName]"
                    type="date" placeholder="请选择" clearable style="width: 240px" />
                <el-input v-else v-model="state.queryParams[item.fieldName]" :placeholder="'请输入'" clearable
                    @keyup.enter.native="handleQuery()" />
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="handleQuery()">
                    <SvgIcon name="elementSearch" />搜索
                </el-button>
                <el-button @click="resetQuery">
                    <SvgIcon name="elementRefresh" />
                    重置
                </el-button>
            </el-form-item>
        </el-form>
    </el-card>
    <div v-loading="state.loading" style="min-height: 600px">
        <el-card class="box-card" v-if="filteredListData.length">
            <template #header>
                <div class="card-header">
                    <span class="card-header-text">所有工时</span>
                    <div>
                        <el-button type="primary" plain v-auth="`OaPmTimeAll:add`" @click="onOpenAddModule">
                            <SvgIcon name="elementPlus" />新增
                        </el-button>
                        <el-button type="success" plain @click="onExport">
                            <SvgIcon name="elementDownload" />导出
                        </el-button>
                        <el-button type="danger" plain v-auth="`OaPmTimeAll:delete`" :disabled="state.multiple"
                            @click="onTableRowDel">
                            <SvgIcon name="elementDelete" />删除
                        </el-button>
                        <el-button type="info" plain @click="onOpenAIDialog">
                            <SvgIcon name="elementPlus" />问AI
                        </el-button>
                    </div>
                </div>
            </template>
            <!--数据表格-->

            <el-table :data="state.tableData" @selection-change="handleSelectionChange">
                <el-table-column type="selection" width="55" align="center" />
                <el-table-column v-for="(item, index) in filteredListData" :key="index" :label="item.columnTitle"
                    :width="item.htmlWidth ? item.htmlWidth : ''" :prop="item.fieldName">
                    <template #default="scope" v-if="$slots[item.fieldName]">
                        <slot :name="item.fieldName" :row="scope.row"></slot>
                    </template>
                    <template #default="scope" v-if="item.dictType">
                        <el-tag type="success" disable-transitions>{{ findDictLabel(scope.row[item.fieldName],
                            item.fieldName) || '-- --' }}</el-tag>
                    </template>
                </el-table-column>
                <!-- <el-table-column label="创建时间" align="center" prop="createTime" width="180">
                    <template #default="scope">
                        <span>{ { dateStrFormat(scope.row.createTime) } }</span>
                    </template>
                </el-table-column> -->
                <el-table-column label="操作" align="center" v-auth="`OaPmTimeAll:delete` && `OaPmTimeAll:edit`"
                    class-name="small-padding fixed-width">
                    <template #default="scope">
                        <div style="display: flex; align-items: center; justify-content: center; gap: 10px">
                            <div>
                                <el-tooltip class="box-item" effect="dark" content="编辑" placement="top-start">
                                    <el-button type="primary" size="small" circlesize="small" circle
                                        v-auth="`OaPmTimeAll:edit`" @click="onOpenEditModule(scope.row)">
                                        <SvgIcon :size="12" name="elementEdit" />
                                    </el-button>
                                </el-tooltip>
                            </div>
                            <div>
                                <slot name="listBtn" :row="scope.row"></slot>
                            </div>
                            <div>
                                <el-tooltip class="box-item" effect="dark" content="删除" placement="top-start">
                                    <el-button type="danger" size="small" circle v-auth="`OaPmTimeAll:delete`"
                                        @click="onTableRowDel(scope.row)">
                                        <SvgIcon :size="12" name="elementDelete" />
                                    </el-button>
                                </el-tooltip>
                            </div>
                        </div>
                    </template>
                </el-table-column>
            </el-table>
            <!-- 分页设置-->
            <div v-show="state.total > 0">
                <el-divider></el-divider>
                <el-pagination background :total="state.total" :current-page="state.queryParams.pageNum"
                    :page-size="state.queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper"
                    @size-change="handleSizeChange" @current-change="handleCurrentChange" />
            </div>
        </el-card>
    </div>
    <EditModule ref="editModuleRef" />
    <!-- AI对话弹窗 -->
    <el-dialog style="background: #f7f7f7;position: relative" v-model="aiDialogVisible" title="AI对话" fullscreen
        :close-on-click-modal="false">
        <div style="padding: 10px;margin-bottom: 10px; max-height: 500px; overflow-y: auto;">
            <div v-for="(msg, idx) in parsedMarkdown" :key="idx" v-show="msg.role !== 'tool'"
                :style="{ textAlign: msg.role === 'user' ? 'right' : 'left', margin: '8px 0' }">
                <span v-html="msg.content" v-loading="msg.msgLoading"
                    :style="{ background: msg.role === 'user' ? '#d9f7be' : '#fff', padding: '10px 30px', borderRadius: '4px', display: 'inline-block', fontSize: '20px' }">
                </span>
            </div>
        </div>
        <div class="input-box">
                <el-input :input-style="{border:'none',outline:'none'}" type="textarea" v-model="aiInput" placeholder="请输入您的问题" @keyup.enter="sendAIMessage" clearable
                    style="margin-bottom: 10px;" />
                <el-button type="primary" @click="sendAIMessage" :loading="aiLoading">发送</el-button>
        </div>
    </el-dialog>
</template>

<script lang="ts" setup name="Partner">
import { reactive, ref, onMounted, getCurrentInstance, onUnmounted, computed, watch } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import EditModule from './component/editModule.vue';
import { del, get, getStruct } from '@/api/public/basic';
import MarkdownIt from 'markdown-it';
import hljs from 'highlight.js';
import { useRoute } from 'vue-router';
import 'highlight.js/styles/atom-one-dark.css';

const { proxy } = getCurrentInstance() as any;
const editModuleRef = ref();
const route = useRoute();
const md = new MarkdownIt({
    html: true,
    linkify: true,
    typographer: true,
    highlight: function (str, lang) {
        if (lang && hljs.getLanguage(lang)) {
            try {
                return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
            } catch (__) { }
        }
        return ''; // 使用默认的高亮处理
    }
});


// AI对话弹窗相关
const aiDialogVisible = ref(false);
const aiMessages = ref<{ role: string, content: string, arguments?: string, tool_calls?: any, tool_call_id?: any, msgLoading?: boolean }[]>([]);
const aiInput = ref('');
const aiLoading = ref(false);

interface TableStructItem {
    columnTitle: string;
    fieldName: string;
    htmlType?: string;
    dictType?: string;
    htmlWidth?: string | number;
    isQuery?: boolean;
    isList?: boolean;
    [key: string]: any;
}
const TOOL = ["read_file", "write_file","query_data"] // 工具列表

function onOpenAIDialog() {
    aiDialogVisible.value = true;
    openMcpSse();
}

// MCP服务器SSE相关
const mcpSseUrl = 'http://localhost/sse';
let mcpEventSource: EventSource | null = null;
const mcpMessages = ref<string[]>([]);
const mcpTools = ref<any[]>([]); // MCP工具列表
const curMcpTool = ref<any>(null); // 当前选中的MCP工具

async function openMcpSse() {
    if (mcpEventSource) return;
    mcpEventSource = new window.EventSource(mcpSseUrl);
    mcpEventSource.addEventListener('endpoint', async (event) => {
        // 拿到 endpoint 后，立即用其查询 tools
        state.sessionAddr = event.data;
        try {
            const resp = await fetch(`http://localhost${event.data}`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    "method": "tools/list",
                    "jsonrpc": "2.0",
                    "id": 1,
                    "params": {
                        "_meta": {
                            "progressToken": 1
                        }
                    }
                })
            });
            if (!resp.ok) throw new Error('获取工具列表失败');
        } catch (e) {
            aiMessages.value.push({ role: 'mcp', content: '[MCP] 通过endpoint获取工具列表失败' });
        }
    })
    mcpEventSource.onmessage = async (event) => {
        // 这里可根据协议解析event.data
        try {
            const data = JSON.parse(event.data);
            // 工具列表响应
            if (Array.isArray(data?.result?.tools)) {
                mcpTools.value = data.result.tools;
                // aiMessages.value.push({ role: 'mcp', content: '[MCP] 工具列表已更新' });
            } else {
                const data = JSON.parse(event.data);
                mcpMessages.value.push(JSON.stringify(data));
                // 拿到工具调用后返回的数据再次发送给AI
                aiMessages.value.push({ role: 'tool', content: JSON.stringify(data.result.content), });
                // aiMessages.value.push({ role: 'mcp', content: '[MCP] ' + (data.content || event.data) });
                console.log('MCP消息:', aiMessages.value);
                curMcpTool.value = null; // 清空当前工具
                sendAIMessage();
            }
        } catch {
            mcpMessages.value.push(event.data);
            // aiMessages.value.push({ role: 'mcp', content: '[MCP] ' + event.data });
        }
    };
    mcpEventSource.onerror = (err) => {
        // aiMessages.value.push({ role: 'mcp', content: '[MCP] 连接异常' });
        closeMcpSse();
    };
}

function closeMcpSse() {
    if (mcpEventSource) {
        mcpEventSource.close();
        mcpEventSource = null;
    }
}

// 调用工具
const callTool = async () => {
    if (!state.sessionAddr) return;
    const filePath = curMcpTool.value?.function.arguments
    try {
        const resp = await fetch(`http://localhost${state.sessionAddr}`, {
            method: 'POST',
            body: JSON.stringify({
                "method": "tools/call",
                "params": {
                    "name": curMcpTool.value?.function.name,
                    "arguments": filePath ? JSON.parse(filePath) : null,
                    "_meta": {
                        "progressToken": 0
                    }
                },
                "jsonrpc": "2.0",
                "id": 2
            })
        }); // 这里根据协议构造请求
        if (!resp.ok) throw new Error('调用工具失败');

    } catch (e) {
        aiMessages.value.push({ role: 'mcp', content: '[MCP] 调用工具失败' });
    }
}

// 弹窗关闭时断开MCP SSE
watch(aiDialogVisible, (val) => {
    if (!val) {
        closeMcpSse();
    }
});

// 修正类型推断
let filteredQueryData = computed<TableStructItem[]>(() => {
    return (state.tableStructData as TableStructItem[]).filter((item) => item.isQuery) || [];
});
let filteredListData = computed<TableStructItem[]>(() => {
    return (state.tableStructData as TableStructItem[]).filter((item) => item.isList) || [];
});

const parsedMarkdown = computed(() => {
    return aiMessages.value.map((msg) => {
        if (msg.role === 'assistant' || msg.role === 'user') {
            return {
                ...msg,
                content: md.render(msg.content)
            }
        }
        return msg;
    })
});

const xmlToJson = (xml) => {
    console.log('xml', xml);
  // 创建 DOMParser 实例
  const parser = new DOMParser();
  // 解析 XML 字符串
  const xmlDoc = parser.parseFromString(xml, "text/xml");
  
  // 递归转换函数
  function parseNode(node) {
    const obj = {};
    
    // 处理元素节点
    if (node.nodeType === 1) {
      // 处理属性
      if (node.attributes.length > 0) {
        obj["@attributes"] = {};
        for (let j = 0; j < node.attributes.length; j++) {
          const attribute = node.attributes[j];
          obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
        }
      }
    } 
    // 处理文本节点
    else if (node.nodeType === 3) {
      return node.nodeValue.trim();
    }
    
    // 处理子节点
    const textNodes = [].slice.call(node.childNodes).filter(n => n.nodeType === 3);
    const elementNodes = [].slice.call(node.childNodes).filter(n => n.nodeType === 1);
    
    if (textNodes.length > 0) {
      obj["text"] = textNodes[0];
    }
    
    for (let i = 0; i < elementNodes.length; i++) {
      const child = elementNodes[i];
      const nodeName = child.nodeName;
      
      if (typeof obj[nodeName] === "undefined") {
        obj[nodeName] = parseNode(child);
      } else {
        if (!Array.isArray(obj[nodeName])) {
          obj[nodeName] = [obj[nodeName]];
        }
        obj[nodeName].push(parseNode(child));
      }
    }
    
    return obj;
  }
  
  return parseNode(xmlDoc);
}

// 发送AI消息
async function sendAIMessage() {
    // if (!aiInput.value.trim()) return;
    state.msgLoading = true;
    const userMsg = aiInput.value;
    aiInput.value && aiMessages.value.push({ role: 'user', content: `<task>\n${userMsg}\n<\\task>`, msgLoading: false });
    aiLoading.value = true;
    aiInput.value = '';

    // 构造带tools的messages
    let messages = [...aiMessages.value];
    if (mcpTools.value.length > 0) {
        const toolListStr = mcpTools.value.map(t => `${t.name || t.id}: ${t.description || ''}`).join('\n');
        messages = [
            { role: 'system', content: '可用工具列表：\n' + toolListStr },
            ...messages
        ];
    }
    // 先插入一条空的assistant消息用于流式追加
    aiMessages.value.push({ role: 'assistant', content: '', msgLoading: true });
    const aiMsgIdx = aiMessages.value.length - 1;

    try {
        const response = await fetch('http://localhost/api/ai/v1/chat/completions', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                model: 'x-basic',
                stream: true,
                tool_choice: 'compatible',
                tools: mcpTools.value.map(t => ({
                    type: 'function',
                    function: {
                        name: t.name,
                        description: t.description || '',
                        parameters: t.inputSchema
                    }
                })),
                messages
            })
        });
        if (!response.ok) {
            const errorData = await response.json().catch(() => null);
            throw new Error(errorData?.message || `AI接口请求失败: ${response.status} ${response.statusText}`);
        }



        // SSE流式解析
        const reader = response.body?.getReader();
        if (!reader) throw new Error('无法读取AI响应流');

        let aiReply = '';
        let aiReplyArgs = '';
        let decoder = new TextDecoder();
        let buffer = '';
        aiMessages.value[aiMsgIdx].msgLoading = false;
        while (true) {
            const { value, done } = await reader.read();
            if (done) break;

            buffer += decoder.decode(value, { stream: true });

            // 处理可能的多条消息
            const lines = buffer.split('\n');
            buffer = lines.pop() || ''; // 保存不完整的行

            for (const line of lines) {
                const trimmedLine = line.trim();
                if (!trimmedLine || trimmedLine === 'data: [DONE]') continue;

                if (trimmedLine.startsWith('data:')) {
                    try {
                        const jsonStr = trimmedLine.replace(/^data:\s*/, '');
                        const data = JSON.parse(jsonStr);
                        // 处理内容增量
                        if (data.choices?.[0]?.delta?.content) {
                            aiReply += data.choices[0].delta.content;
                            aiMessages.value[aiMsgIdx].content = aiReply;
                            console.log('aiReply:', aiReply);
                        }

                        if (data.choices[0]?.delta?.tool_calls[0]?.function?.arguments) {
                            aiReplyArgs += data.choices[0]?.delta?.tool_calls[0]?.function?.arguments;
                            aiMessages.value[aiMsgIdx].arguments = aiReplyArgs;
                            console.log('aiReplyArgs:', aiReplyArgs);
                        }

                        // 处理工具调用
                        if (data.choices?.[0]?.delta?.tool_calls) {
                            const toolCall = data.choices[0].delta.tool_calls[0];
                            if (toolCall.function?.name) {
                                console.log('检测到工具调用:', toolCall);
                                curMcpTool.value = toolCall
                                // 当前调用工具类型
                                // curMcpTool.value = toolCall.function.name;
                            }
                        }
                    } catch (e) {
                        console.error('解析SSE数据失败:', e, '原始数据:', trimmedLine);
                    }
                }
            }
        }

        // 确保最后的数据被处理
        if (buffer) {
            const trimmedLine = buffer.trim();
            if (trimmedLine.startsWith('data:')) {
                try {
                    const jsonStr = trimmedLine.replace(/^data:\s*/, '');
                    const data = JSON.parse(jsonStr);
                    if (data.choices?.[0]?.delta?.content) {
                        aiReply += data.choices[0].delta.content;
                        aiMessages.value[aiMsgIdx].content = aiReply;
                    }
                } catch (e) {
                    console.error('解析最后一条SSE数据失败:', e);
                }
            }
        }

        if (!aiReply) {
            aiMessages.value[aiMsgIdx].content = '';
        }

        // 检查回复中是否包含工具调用
        if (curMcpTool.value) {
            const lastMessage = aiMessages.value[aiMsgIdx];
            console.log('检测到工具调用:', curMcpTool.value);
            console.log('lastMessage:', lastMessage);
            // 获取filePath
            const filePath = lastMessage.arguments ? lastMessage.arguments : null
            curMcpTool.value.function.arguments = filePath;
            aiMessages.value[aiMsgIdx] = { role: 'assistant', content: '[AI工具调用]', tool_calls: [curMcpTool.value] };
            callTool();
        }
    } catch (e: any) {
        console.error('AI请求异常:', e);
        aiMessages.value.push({
            role: 'assistant',
            content: `AI接口异常: ${e.message || '未知错误'}`
        });
    } finally {
        aiLoading.value = false;
        console.log('AI对话记录:', aiMessages.value);
        const asLastMessage = aiMessages.value[aiMessages.value.length - 1].role == 'assistant' ? aiMessages.value[aiMessages.value.length - 1] : null
        const newContent = asLastMessage?.content

        const extXml = extractToolXml(newContent, TOOL)
        const xmlObj = xmlToJson(extXml) || null
        console.log('extXml------:',xmlObj);
        // 判断是否为调用工具指令
        const toolName = xmlObj ? Object.keys(xmlObj)[0]: ''
        console.log('检测到工具调用:', toolName);
        if (TOOL.includes(toolName)) {
            const secName = xmlObj[toolName] ? Object.keys(xmlObj[toolName]).find((item) => !item.includes('text')) : null
            console.log('secName:',secName);
            curMcpTool.value = {
                function: {
                    name: toolName,
                    arguments: secName ? JSON.stringify({
                        [secName]: xmlObj[toolName][secName]['text']?.data
                    }) : null
                }
            }
            callTool();
        }
    }
}


const extractToolXml=(str, tools)=> {
    // 遍历所有工具
    for (const tool of tools) {
        // 创建匹配工具标签的正则表达式
        const regex = new RegExp(`<${tool}[^>]*>[\\s\\S]*?<\\/${tool}>`, 'i');
        const match = str.match(regex);
        
        if (match) {
            return match[0]; // 返回匹配到的整个XML片段
        }
    }
    return null; // 没有找到任何工具
}




const state = reactive({
    // 弹出层标题
    title: '',
    // 是否显示弹出层
    open: false,
    // 表格数据
    tableData: [],
    // 总条数
    total: 0,
    // 表格列数据
    tableStructData: [],
    // 选中数组
    ids: [],
    // 非单个禁用
    single: true,
    // 非多个禁用
    multiple: true,
    loading: false,
    msgLoading: false,
    // 查询参数
    queryParams: {
        // 页码
        pageNum: 1,
        // 每页大小
        pageSize: 10,
    },
    sessionAddr: '',
});

// 可搜索的字段
/* 原有的filteredQueryData和filteredListData定义已被类型化版本替换，无需重复声明 */

/** 查询列表 */
const handleQuery = () => {
    const params = state.queryParams;
    state.loading = true;
    // 如果state.queryParams中有空值，则删除
    Object.keys(params).forEach((key) => {
        if (!params[key]) {
            delete params[key];
        }
    });
    get(params, 'hS9i1PUcWXs').then((response: any) => {
        state.tableData = response.data.result;
        state.total = response.data.count;
        state.loading = false;
    });

};

onMounted(() => {
    // 获取data struct
    getStruct('hS9i1PUcWXs').then((res: any) => {
        if (res.status == 0) {
            const arr = res.data.map((item: any) => {
                if (item.isQuery) {
                    state.queryParams[item.fieldName] = undefined;
                }
                // 如果存在字典数据
                if ((item.isQuery || item.isList) && item.dictType) {
                    proxy.getDicts(item.dictType).then((response: any) => {
                        state[item.fieldName + 'Option'] = response.data;
                    });
                }

                return item;
            });
            arr.sort(compareSorts);
            state.tableStructData = arr;
            handleQuery();
        }
    });
})

/** 字典label */
const findDictLabel = (dictValue, fieldName) => {
    if (!state[fieldName + 'Option']) return;
    const obj = state[fieldName + 'Option'].find((item) => item.dictValue == dictValue) || {};
    return obj.dictLabel;
};

/**比较两个对象的 sort 属性*/
const compareSorts = (a: any, b: any) => {
    // 如果 sort 属性为 null，则将其视为无穷大
    if (a.sort === null || b.sort === null) {
        return 0; // 不参与排序
    }
    // 比较 sort 属性
    return a.sort - b.sort;
};

/** 重置按钮操作 */
const resetQuery = () => {
    state.tableStructData.forEach((item: any) => {
        if (item.isQuery) {
            state.queryParams[item.columnName] = undefined;
        }
        return item;
    });
    handleQuery();
};

const handleCurrentChange = (val: number) => {
    state.queryParams.pageNum = val;
    handleQuery();
};
const handleSizeChange = (val: number) => {
    state.queryParams.pageSize = val;
    handleQuery();
};

// 打开新增配置参数弹窗
const onOpenAddModule = () => {
    state.title = '创建公共文件';
    editModuleRef.value.openDialog({});
};



// 打开编辑配置参数弹窗
const onOpenEditModule = (row: object) => {
    state.title = '修改文件名';
    editModuleRef.value.openDialog(row);
};

/** 删除按钮操作 */
const onTableRowDel = (row: any) => {
    const ids = row['flowId'] ? [row['flowId']] : state.ids;

    ElMessageBox({
        message: '是否确认删除的数据项?',
        title: '警告',
        showCancelButton: true,
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
    }).then(function () {
        return del({ ['flowId']: ids }, 'hS9i1PUcWXs').then(() => {
            handleQuery();
            ElMessage.success('删除成功');
        });
    });
};
// 多选框选中数据
const handleSelectionChange = (selection: any) => {
    state.ids = selection.map((item: any) => {
        return item['flowId'];
    });

    state.single = selection.length != 1;
    state.multiple = !selection.length;
};
// 页面加载时
onMounted(() => { });
// 页面卸载时
onUnmounted(() => {
    proxy.mittBus.off('onEditModule');
});

// 导出功能
const onExport = async () => {
    // 判断是否有勾选项
    let list: any[] = [];
    if (state.ids && state.ids.length > 0) {
        // 有勾选项，仅导出勾选数据
        list = (state.tableData as unknown as any[]).filter((row: any) => (state.ids as unknown as any[]).includes(row.flowId));
        if (!list.length) {
            ElMessage.warning('无可导出数据');
            return;
        }
    } else {
        // 无勾选项，导出全部数据
        const params = { ...state.queryParams };
        Object.keys(params).forEach((key) => {
            if (!params[key]) {
                delete params[key];
            }
        });
        // 先获取总数
        const countRes = await get(params, 'hS9i1PUcWXs');
        const total = countRes.data.count;
        if (!total) {
            ElMessage.warning('无可导出数据');
            return;
        }
        // 拉取所有数据
        const allParams = { ...params, pageNum: 1, pageSize: total };
        const allRes = await get(allParams, 'hS9i1PUcWXs');
        list = allRes.data.result || [];
    }
    // 处理表头和字段
    const columns = filteredListData.value;
    const header = columns.map((col) => col.columnTitle);
    const fields = columns.map((col) => col.fieldName);
    // 处理字典类型显示
    const rows = list.map((row) => {
        return fields.map((field, idx) => {
            const col = columns[idx];
            if (col.dictType) {
                return findDictLabel(row[field], field) || '';
            }
            return row[field] !== undefined ? row[field] : '';
        });
    });
    // 生成CSV内容
    let csvContent = '\uFEFF' + header.join(',') + '\n';
    rows.forEach((row) => {
        csvContent += row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',') + '\n';
    });
    // 下载CSV
    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = "所有工时" + " - " + new Date().toLocaleString().replace(/\//g, '-') + ' - 导出数据' + '.csv';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
};
</script>
<style scoped>
/* 链接悬停效果 */
a:hover {
    text-decoration: underline;
}
.input-box{
    
    position: absolute;
    width: 64%;
    bottom: 113px;
    background: #fff;
    padding: 25px;
    border-radius: 10px;
    box-shadow: 1px 0px 9px #eee;
    transform:translateX(-50%);
    left: 50%;
    display: flex;
    align-items: center;
    gap: 20px;
    
}
:deep(.el-textarea__inner){
    /* 取消边框 */
  border: none !important;
  resize: none !important;
  box-shadow: none !important;
}

</style>
